#!/usr/bin/env python

'''
Python-Tail - Unix tail follow implementation in Python.

python-tail can be used to monitor changes to a file.

Example:
    import tail

    # Create a tail instance
    t = tail.Tail('file-to-be-followed')

    # Register a callback function to be called when a new line is found in the followed file.
    # If no callback function is registerd, new lines would be printed to standard out.
    t.register_callback(callback_function)

    # Follow the file with 5 seconds as sleep time between iterations.
    # If sleep time is not provided 1 second is used as the default time.
    t.follow(s=5) '''

# Author - Kasun Herath <kasunh01 at gmail.com>
# Source - https://github.com/kasun/python-tail

import commands
import os
import sys
import time
import datetime
import re


class TailError(Exception):
    def __init__(self, msg):
        self.message = msg

    def __str__(self):
        return self.message


class Tail(object):
    ''' Represents a tail command. '''

    def __init__(self, tailed_file):
        ''' Initiate a Tail instance.
            Check for file validity, assigns callback function to standard out.

            Arguments:
                tailed_file - File to be followed.
        '''

        self.check_file_validity(tailed_file)
        self.tailed_file = tailed_file
        self.callback = sys.stdout.write

    def follow(self, s=1):
        ''' Do a tail follow. If a callback function is registered it is called with every new line.
        Else printed to standard out.

        Arguments:
            s - Number of seconds to wait between each iteration; Defaults to 1.
        '''

        with open(self.tailed_file) as file_:
            # Go to the end of file
            file_.seek(0, 2)
            eventDic = {}
            tmpSQL = []
            events = []
            while True:

                curr_position = file_.tell()
                line = file_.readline()
                if not line:
                    file_.seek(curr_position)
                    print(events)
                    del events[:]
                    time.sleep(s)
                else:
                    self.callback(line, eventDic, tmpSQL, events)

    def register_callback(self, func):
        ''' Overrides default callback function to provided function. '''
        self.callback = func

    def check_file_validity(self, file_):
        ''' Check whether the a given file exists, readable and is a file '''
        if not os.access(file_, os.F_OK):
            raise TailError("File '%s' does not exist" % (file_))
        if not os.access(file_, os.R_OK):
            raise TailError("File '%s' not readable" % (file_))
        if os.path.isdir(file_):
            raise TailError("File '%s' is a directory" % (file_))


def set_datetime_by_time_line(eventDic, line):
    eventDic.clear()
    time_str = "20" + line.replace("# Time:", "").replace("  ", " ").strip()
    query_time = datetime.datetime.strptime(time_str, "%Y%m%d %H:%M:%S")
    eventDic['time'] = query_time


def set_query_info(eventDic, line):
    tmp_line = line.replace("#", "").replace(": ", ":").replace("  ", " ").replace("  ", " ")
    tmp_items = tmp_line.split(" ")
    for tmp_item in tmp_items:
        # print(tmp_item)
        if tmp_item.strip().startswith("Query_time:"):
            eventDic['query_time'] = tmp_item.replace("Query_time:", "")
        if tmp_item.strip().startswith("Lock_time:"):
            eventDic['lock_time'] = tmp_item.replace("Lock_time:", "")
        if tmp_item.strip().startswith("Rows_sent:"):
            eventDic['rows_sent'] = tmp_item.replace("Rows_sent:", "")
        if tmp_item.strip().startswith("Rows_examined:"):
            eventDic['rows_examined'] = tmp_item.replace("Rows_examined:", "")


def set_login_user_ip(eventDic, line):
    tmp_line = line.replace("#", "").replace(": ", ":").replace("  ", " ").replace("  ", " ")
    result = re.findall("(\:)", tmp_line)
    eventDic['user'] = result[0]
    eventDic['ip'] = result[1]


def set_schema_error_killed(eventDic, line):
    tmp_line = line.replace("#", "").replace(": ", ":").replace("  ", " ").replace("  ", " ")
    result = re.findall(":([\S]+)", tmp_line)
    eventDic['schema'] = result[0]
    eventDic['last_errno'] = result[1]
    eventDic['killed'] = result[2]


def set_bytes_sent(eventDic, line):
    tmp_line = line.replace("#", "").replace(": ", ":").replace("  ", " ").replace("  ", " ")
    result = re.findall(":([\S]+)", tmp_line)
    eventDic['bytes_sent'] = result[0]


def set_sql(eventDic, line, tmpSQL, events):
    if line.find(';') != -1:
        fullSQL = ""
        tmpSQL.append(line)
        fullSQL = re.sub(r' {1,}', " ", "".join(tmpSQL).replace("\n", " "))
        eventDic['sql'] = fullSQL
        fingerprintSQL = commands.getstatusoutput("/usr/bin/pt-fingerprint --query '" + fullSQL + "'")[1]
        print(fingerprintSQL)
        eventDic['fingerprint'] = fingerprintSQL
        events.append(eventDic.copy())
        del tmpSQL[:]
        eventDic.clear()
    else:
        tmpSQL.append(line)


def set_timestamp(eventDic, line):
    result = re.findall("=([\S]+);", line)
    eventDic['timestamp'] = result[0]


def paserQueryLog(line, eventDic, tmpSQL, events):
    if line.startswith("# Time:"):
        set_datetime_by_time_line(eventDic, line)
    elif line.startswith("# Query_time:"):
        set_query_info(eventDic, line)
    elif line.startswith("# User@Host:"):
        set_login_user_ip(eventDic, line)
    elif line.startswith("# Schema:"):
        set_schema_error_killed(eventDic, line)
    elif line.startswith("# Bytes_sent:"):
        set_bytes_sent(eventDic, line)
    elif line.startswith("SET timestamp"):
        set_timestamp(eventDic, line)
    else:
        set_sql(eventDic, line, tmpSQL, events)


def main():
    t = Tail('/opt/app/percona_3306/logs/slow.log')
    t.register_callback(paserQueryLog)
    t.follow(s=5)


if __name__ == '__main__':
    main()

